package com.atguigu.gmall.index.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;


@Component
public class DistributedLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 加锁
     *
     * @param lockName 锁名(key)
     * @param uuid     values
     * @param expire   过期时间
     * @return
     */
    public Boolean tryLock(String lockName, String uuid, Integer expire) {
        /**
         * 1.先判断锁是否存在（exists == 0），如果不存在则直接获取锁（hset）
         * 2.如果锁存在，则判断是否自己的锁（hexists == 1），如果是自己的锁则重入（hincrby）
         * 3.否则获取锁失败，返回0
         */
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";

        //获取锁失败则  sleep 50 再重新尝试获取
        while (!this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString())) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 获取锁成功，则定时重置过期时间
        this.renewExpire(lockName, uuid, expire);
        return true;
    }

    public void unlock(String lockName, String uuid) {
        /**
         * 1.判断当前线程的锁是否存在（hexists），则直接返回nil
         * 2.否则，直接减1(hincrby -1)。判断减1后的值是否为0，如果为0则释放锁（del），返回1
         * 3.则直接返回0
         */
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";

        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);

        if (flag == null) {
            throw new IllegalMonitorStateException("你要释放的锁不属于你！");
        }
    }


    /**
     * 续期 :
     * 判断是否是自己的锁,如果是则重置过期时间
     *
     * @param lockName
     * @param uuid
     * @param expire
     */
    private void renewExpire(String lockName, String uuid, Integer expire) {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";

        //定时任务
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString())) {
                    renewExpire(lockName, uuid, expire);
                }
            }
        }, expire * 1000 / 3);
    }

    public static void main(String[] args) {
        System.out.println("定时任务的初始化时间：" + System.currentTimeMillis());
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行lua脚本，重置过期时间。" + System.currentTimeMillis());
            }
        }, 10000, 10000); // 延迟多久(毫秒)    周期时间
    }
}
